﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Routing;

using RushTracker.Core;
using RushTracker.Core.BEncoding;
using RushTracker.Core.Data;
using RushTracker.Core.Util;
using RushTracker.Core.Models.Torrents;
using System.IO;
using System.Text;
using System.Globalization;
using RushTracker.Core.Models;
using System.Net.Sockets;

namespace RushTracker.Web.UI.Handlers
{
    public class AnnounceHandler : IHttpHandler
    {

        public void ProcessRequest(HttpContext context)
        {
            context.Response.Clear();
            context.Response.ContentType = "text/plain";

            TrackerAnnounceRequest announceRequest = new TrackerAnnounceRequest(context.Request);

            string key = context.Request.RequestContext.RouteData.Values["key"].ToString();

            // should check if anonymous announce requests are allowed

            IDataRepository data = IoC.Resolve<IDataRepository>();
            User requestingUser = data.GetUserByKey(key);

            if (requestingUser == null)
            {
                Fail(context, "Invalid request");
                return;
            }

            if (!requestingUser.IsActivated)
            {
                Fail(context, "User is not activated. Denied.");
                return;
            }

            Torrent t = data.GetTorrentByInfoHash(announceRequest.InfoHash);

            if (t == null)
            {
                Fail(context, "Torrent not associated with this tracker.");
                return;
            }

            // get peers
            int limit = t.Seeders + t.Leechers;
            if (limit > announceRequest.RequestedPeerCount)
                limit = announceRequest.RequestedPeerCount;

            IList<Peer> peers = t.Information.Peers.Where(p => p.Connectable).Take(limit).ToList();
            Peer me = null;

            BEncodedDictionary rd = new BEncodedDictionary();
            rd.Add(new BEncodedString("interval"), new BEncodedNumber(600)); // read from settings here

            // add peers
            BEncodedList bencpeers = new BEncodedList(peers.Count);

            foreach (Peer p in peers)
            {
                if (p.PeerId == announceRequest.PeerId)
                    me = p;

                BEncodedDictionary pdict = new BEncodedDictionary();
                pdict.Add(new BEncodedString("peer id"), new BEncodedString(p.PeerId));
                pdict.Add(new BEncodedString("ip"), new BEncodedString(p.IPAddress));
                pdict.Add(new BEncodedString("port"), new BEncodedNumber(p.Port));

                bencpeers.Add(pdict);
            }

            rd.Add(new BEncodedString("peers"), bencpeers);

            if (me == null)
                me = t.Information.Peers.FirstOrDefault(p => p.PeerId == announceRequest.PeerId);

            // up/down stats
            if (me == null)
            {
                //
            }
            else
            {
                long downme = Math.Max(0, announceRequest.Downloaded - (long)me.Downloaded);
                long upme = Math.Max(0, announceRequest.Uploaded - (long)me.Uploaded);

                if (downme > 0 || upme > 0)
                {
                    requestingUser.Downloaded += downme;
                    requestingUser.Uploaded += upme;

                    data.UpdateObject(requestingUser);
                    data.Flush();
                }
            }

            if (announceRequest.Event == AnnounceRequestEvent.Stopped)
            {
                if (me != null)
                {
                    if (me.Seeder)
                    {
                        t.Seeders -= 1;
                    }
                    else
                    {
                        t.Leechers -= 1;
                    }

                    data.DeleteObject(me);
                    data.Flush();
                }
            }
            else
            {
                if (announceRequest.Event == AnnounceRequestEvent.Completed)
                    t.TimesCompleted += 1;

                if (me != null)
                {
                    bool seeder = (announceRequest.Left == 0);
                    bool preUpdateSeeder = me.Seeder;

                    me.Downloaded = (ulong)announceRequest.Downloaded;
                    me.Uploaded = (ulong)announceRequest.Uploaded;
                    me.Remaining = (ulong)announceRequest.Left;
                    me.LastAction = DateTime.Now;
                    me.Seeder = seeder;

                    if (seeder && !me.Seeder)
                        me.Finished = DateTime.Now;

                    data.UpdateObject(me);
                    data.Flush();

                    if (!preUpdateSeeder)
                    {
                        if (seeder)
                        {
                            t.Seeders += 1;
                            t.Leechers -= 1;
                        }
                        else
                        {
                            t.Seeders -= 1;
                            t.Leechers += 1;
                        }
                    }
                }
                else
                {
                    if (announceRequest.Event != AnnounceRequestEvent.Started)
                    {
                        Fail(context, "Peer not found. Restart torrent.");
                        return;
                    }

                    bool connectable = false;

                    if (announceRequest.Port == 1) // check blacklist
                    {
                        Fail(context, String.Format("Port '{0}' is blacklisted.", announceRequest.Port));
                    }
                    else
                    {
                        try
                        {
                            Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                            s.Connect(announceRequest.IPAddress, announceRequest.Port);

                            if (s.Connected)
                                connectable = true;
                        }
                        catch (Exception)
                        {
                        }
                    }

                    me = new Peer();
                    me.Connectable = connectable;
                    me.Information = t.Information;
                    me.PeerId = announceRequest.PeerId;
                    me.IPAddress = announceRequest.IPAddress;
                    me.Port = announceRequest.Port;
                    me.Uploaded = (ulong)announceRequest.Uploaded;
                    me.Downloaded = (ulong)announceRequest.Downloaded;
                    me.Remaining = (ulong)announceRequest.Left;
                    me.LastAction = DateTime.Now;
                    me.Seeder = (announceRequest.Left == 0);
                    me.User = requestingUser;
                    me.Agent = context.Request.UserAgent;

                    data.SaveObject(me);

                    if (me.Id > 0)
                    {
                        if (announceRequest.Left == 0)
                        {
                            t.Seeders += 1;
                        }
                        else
                        {
                            t.Leechers += 1;
                        }
                    }
                }
            }

            data.UpdateObject(t);
            data.Flush();

            context.Response.BinaryWrite(rd.Encode());
        }

        private void Fail(HttpContext context, string p)
        {
            BEncodedDictionary rd = new BEncodedDictionary();
            rd.Add(new BEncodedString("failure reason"), new BEncodedString(p));

            context.Response.BinaryWrite(rd.Encode());
            context.Response.End();
        }

        public bool IsReusable
        {
            get
            {
                return false;
            }
        }
    }
}